#include "game.h"
#include <string>
#include <fstream>

Game::Game(): lives(3){}
Game::~Game()
{
    for (size_t i = 0; i < objects.size(); ++i)
    {
        delete objects.at(i);
    }
}

//loads gamesave file, and passes each line to appropriate constructor
void Game::loadGame()
{
    objects.clear();

    //read saved game from file...
    ifstream infile("savedgame.txt");

    //if there is no previously saved game, start a new game with default difficulty
    if (!infile)
    {
        createWorld(1);
        return;
    }

    //sets pacman and paddles to default positions
    objects.push_back(createObject("PacMan",30,260));
    objects.push_back(createObject("Paddle",0,260));
    objects.at(1)->setYVelocity(0);               //sets left paddle velocity to zero
    objects.push_back(createObject("Paddle",780,260));
    objects.at(2)->setYVelocity(0);               //sets right paddle velocity to zero


    //int numWalls = 0;

    //get difficulty
    string strdiff;
    getline(infile, strdiff);
    int difficulty = stoi(strdiff);

    //get score
    string oldScore;
    getline(infile, oldScore);
    int loadedScore = stoi(oldScore);
    score.setScore(loadedScore);

    //read wall data, and push walls into objects vector
    string imageType;
    int Xposition = 400;
    int Yposition = 300;
    for (int i = 0; i < difficulty; i++ )
    {
        getline(infile, imageType);
        string Xposi;
        getline(infile, Xposi);
        Xposition = stoi(Xposi);
        string Yposi;
        getline(infile, Yposi);
        Yposition = stoi(Yposi);
        if (imageType == "0Wall")
        {
            objects.push_back(createObject("0Wall", Xposition, Yposition));
        }
        else if (imageType == "1Wall")
        {
            objects.push_back(createObject("1Wall", Xposition, Yposition));
        }
        //numWalls++;
    }
    infile.close();

    //populate empty spots with dots
    createDots(difficulty);
}

//saves values for numbers of objects, then iterates through gameObjects, and calls the Save() method for each object.
void Game::saveGame(int difficulty)
{
    ofstream outfile("savedgame.txt");

    //save number of walls
    outfile << difficulty << endl;

    //save old score
    outfile << to_string(score.getScore()) << endl;

    //save type and position of walls
    for (int i = 3; i < difficulty + 3; i++)
    {
        outfile << objects.at(i)->getType() << endl;
        outfile << objects.at(i)->getX() << endl;
        outfile << objects.at(i)->getY() << endl;
    }
    outfile.close();

}

/*builds gameObjects by determining how many of each object should be made,
  assigns values for objects, and calls the appropriate instantiators.
  Also sets PacMan's lives*/
void Game::createWorld(int numWalls)
{
  objects.push_back(createObject("PacMan",30,280));
  objects.push_back(createObject("Paddle",0,260));
  objects.at(1)->setYVelocity(0);                 //sets left paddle velocity to zero
  objects.push_back(createObject("Paddle",780,260));
  objects.at(2)->setYVelocity(0);                 //sets right paddle velocity to zero

    for (int i = 0; i < numWalls; ++i)
    {
        objects.push_back(createObject("Wall",0,0));
    }

    createDots(numWalls);

}

//creates dot everywhere except where walls are located
void Game::createDots(int numWalls)
{
    //rows...
    for (size_t yCoordinate = 0; yCoordinate < 15; yCoordinate++)
    {
        //columns...
        for (size_t xCoordinate = 2; xCoordinate < 18; xCoordinate++)
        {

            WorldObject *dot = createObject("Dot",xCoordinate,yCoordinate);
            bool isCollided = false;

            //check for collisions
            for (int i = 3; i < 3 + numWalls; i++)
            {
                if (collisionTest(dot,objects.at(i)) != 0) {
                    isCollided = true;
                }
            }

            //only push back the dot if no collision is detected
            if (!isCollided) {
                objects.push_back(dot);
            }
            else {
                delete dot;
            }
        }
    }
}

//factory method

WorldObject* Game::createObject(string type, int xPos, int yPos)
{

    if (type == "PacMan")
    {
        return new PacMan("PacMan", ":/images/PacMan/0-PacMan.png", xPos,yPos,4,4);
    }
    else if (type == "Paddle")
    {
        return new Paddle("Paddle",":/images/paddle.png", xPos, yPos, 0);
    }
    else if (type == "Wall")
    {
        //generate new wall type and coordinates first
        vector<int> values = positionWall();
        return new Wall(to_string(values.at(0)) + "Wall", ":/images/Walls/" + to_string(values.at(0)) + "-wall.png", values.at(1), values.at(2));
    }
    else if (type == "0Wall")
    {
        return new Wall("0Wall", ":/images/Walls/0-wall.png", xPos, yPos);
    }
    else if (type == "1Wall")
    {
        return new Wall("1Wall", ":/images/Walls/1-wall.png", xPos, yPos);
    }
    else if (type == "Dot")
    {
        return new Dot("Dot", ":/images/Dot.png", (xPos*40)+15, (yPos*40)+15);
    }
    else
    {
        return NULL;
    }

}

//Randomly chooses one of two kinds of walls, randomly finds new position for it.
vector<int> Game::positionWall()
{
    vector<int> values;
    int flip = rand() % 2;
    values.push_back(flip);
    if (flip == 0)
    {
        values.push_back((((rand() % 15) + 2)*40)+10);
        values.push_back(((rand() % 9) + 2)*40);
    }

    if (flip == 1)
    {
        values.push_back(((rand() % 15) + 2)*40);
        values.push_back((((rand() % 9) + 2)*40)+10);
    }

    return values;
}

/*END support methods*/


//calls WorldObject::move() on dynamic objects in vector
void Game::move()
{
    int collisionValue(0);

    //see if pacman has collided with a paddle. if he has, make him bounce.
    for (size_t i = 1; i < 3; ++i)
    {

        if (collisionValue == 1 && !(objects.at(0)->getX() < 20 && objects.at(0)->getXVelocity() > 0)
                && !(objects.at(0)->getX() > 680 && objects.at(0)->getXVelocity() < 0))
        {
            objects.at(0)->bounce(collisionValue); //bounce(1) effects an x vector sign switch, (always with paddle)
        }
    }

    //check to see if pacman is colliding with anything
    for (size_t i  = objects.size() -1; i > 2; i--)
    {
        collisionValue = collisionTest(objects.at(0), objects.at(i));
        if (collisionValue == 1 || collisionValue == 2)
        {

            //if it's a wall, bounce off it
            if (objects.at(i)->getType() == "0Wall" || objects.at(i)->getType() == "1Wall")
            {
                objects.at(0)->bounce(collisionValue); //bounce() could be 1 or 2, with predictable results

            }
            else if (objects.at(i)->getType() == "Ghost")
            {
                eat(objects.at(i), objects.at(0));
            }
        }

        //if it's a dot, eat the dot
        else if (collisionValue == 3)
        {

            Dot *dot = dynamic_cast<Dot*>(objects.at(i));
            if (!dot->IsEaten())
            {
                eat(objects.at(0), objects.at(i));
                score.updateScore(10);

            }

        }
    }


    for (size_t i = 0; i < objects.size(); ++i)
    {
        if (objects.at(i)->getType() == "PacMan")
        {
            dynamic_cast<PacMan*>(objects.at(i))->updateImage();
            objects.at(i)->move();  //WorldObject method that updates x and y with XVelocity and YVelocity
        }
    }

    for (int i = 1; i < 3; i++) {
        objects.at(i)->movePaddle();
    }

    //collide pacman off the boundaries...
    if ((objects.at(0)->getX() > 760 && objects.at(0)->getXVelocity() > 0)
            || (objects.at(0)->getX() < 0 && objects.at(0)->getXVelocity() < 0)) //yadda yadda horizontal collision test
    {
        collisionValue = 1;
        objects.at(0)->bounce(collisionValue);
        lives--;

    }
    if (objects.at(0)->getY() > 560 || objects.at(0)->getY() < 0) //yadda yadda vertical test
    {
        collisionValue = 2;
        objects.at(0)->bounce(collisionValue);
    }


}

int Game::collisionTest(WorldObject* attacker, WorldObject* victim)
{
    int attackerXPos = attacker->getX(); //attacker's x-position
    int attackerYPos = attacker->getY(); //attacker's y-position

    //test for paddle collisions
    for (int i = 1; i < 3; i++) {
        if ((attackerXPos > 740 && objects.at(i)->getY() < attackerYPos + 40 && (objects.at(i)->getY() + 80) > attackerYPos) ||
                (attackerXPos < 20 && objects.at(i)->getY() < attackerYPos + 40 && (objects.at(i)->getY() + 80) > attackerYPos)) //yadda yadda horizontal collision test
        {
            return 1;
        }
    }

    vector<int> attackerDim = getDimensions(attacker);
    int attackerWidth(attackerDim.at(0));
    int attackerHeight(attackerDim.at(1));

    vector<int> victimDim = getDimensions(victim);
    int victimWidth(victimDim.at(0));
    int victimHeight(victimDim.at(1));

    bool prevColHit = false;
    bool prevRowHit = false;

    //test to see whether the collision should produce a horizontal or vertical bounce
    if ((attackerXPos - attacker->getXVelocity() + attackerWidth) >= victim->getX() &&
            (attackerXPos - attacker->getXVelocity()) <= (victim->getX() + victimWidth))
    {
        //pacman was already in the same column as the obstacle - should bounce vertically
        prevColHit = true;
    }
    else if ((attackerYPos + attacker->getYVelocity() + attackerWidth) >= victim->getY() &&
             (attackerYPos - attacker->getYVelocity()) <= (victim->getY() + victimHeight))
    {
        //pacman was already in the same row as the obstacle - should bounce horizontally
        prevRowHit = true;
    }

    //test for overlap between the attacker and the obstacle
    if ((attackerXPos + 40) >= victim->getX() && attackerXPos <= (victim->getX() + victimWidth)
            && (attackerYPos + attackerHeight) >= victim->getY() && attackerYPos <= (victim->getY() + victimHeight)) /*code to test for same row*/
    {
        if (victim->getType() == "0Wall"  || victim->getType() == "1Wall" || victim->getType() == "Paddle")
        {
            if (prevRowHit)
                return 1;   //will give a horizontal bounce
            else if (prevColHit)
                return 2;   //will give a vertical bounce
        }
        else if (victim->getType() == "Dot")
        {
            return 3;       //pacman will not bounce because it was a dot
        }
    }
        return 0;       //no bounce  
}

//move Paddle based on velocity{} calls WorldObject::move()
//<paddle> must be position of the intended paddle in the objects vector
//direction: either 1 or -1
void Game::movePaddle(int paddle, int direction)
{
    if(direction == 1)
    {
        objects.at(paddle)->incYVelocity(12);
    }
    else if (direction == -1)
    {
        objects.at(paddle)->incYVelocity(-12);
    }
}

//Modular method that can take either PacMan and a dot, or a Ghost and PacMan;
//If PacMan is the first object, the dot is "eaten".  If Ghost is first, PacMan loses a life.
void Game::eat(WorldObject *Predator, WorldObject *Prey)
{
    if (Predator->getType() == "PacMan")
    {
        Dot *dot = dynamic_cast<Dot*>(Prey);
        dot->setEaten(true);
    }

    //Ghost section not implemented, as Ghost functionality could not be added before deadline *sadface*
    else if (Predator->getType() == "Ghost")
    {

    }
}
/* This method exists as a way to allow the Game class to know the dimensions of a given WorldObject,
and not break the constraits of the Model View structure.  This is because the GUI does not require
dimensions to be included in WorldObjects, as it gets that information from stored QPixMaps*/
vector<int> Game::getDimensions(WorldObject* obj)
{
    vector<int> dim;
    //Vertical Walls and Paddles are the same dimensions, 20*80
    if (obj->getType() == "0Wall" || obj->getType() == "Paddle")
    {
        dim.push_back(20);
        dim.push_back(80);
    }
    //Horiontal Walls
    else if (obj->getType() == "1Wall")
    {
        dim.push_back(80);
        dim.push_back(20);
    }
    //Dots
    else if (obj->getType() == "Dot")
    {
        dim.push_back(10);
        dim.push_back(10);
    }
    //Everything else
    else
    {
        dim.push_back(40);
        dim.push_back(40);
    }
    return dim;
}
